/*
    libverbose is a library for use in troubleshooting other software
    Copyright (C) 2008 Brock Noland

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "libverbose.h"
#include "config.h"

#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <syslog.h>
#include <stdlib.h>
#include <ctype.h>

// defines LASTMARK
#include <stropts.h>

/* from glibc 2.7 */
#define INITIAL_ARGV_MAX 1024
/* Stolen (and modifed) from binutils strings
**/
#define STRING_ISGRAPHIC(c) \
        ((c) >= 0 && (c) <= 255 && isprint(c))

void
_init (void)
{
    parse_config ();
}

static char *
strmaxlen (char *str, int length)
{
    if (str && strlen (str) > length)
      {
          str[length] = '\0';
      }
    return str;
}

/* Given a socket address, return a string representation */
static char *
get_ip (const struct sockaddr *address)
{
    if (address->sa_family == AF_INET)
      {
          return (char *) inet_ntoa (((struct sockaddr_in *) address)->
                                     sin_addr);
      }
    /* The code below was copied from vsftpd,
     * but is untested in this context 
     **/
    else if (address->sa_family == AF_INET6)
      {
          static char inaddr_buf[64];
          const char *p_ret = (char *) inet_ntop (AF_INET6,
                                                  &((struct sockaddr_in6 *)
                                                    address)->sin6_addr,
                                                  inaddr_buf,
                                                  sizeof (inaddr_buf));
          inaddr_buf[sizeof (inaddr_buf) - 1] = '\0';
          if (p_ret == NULL)
            {
                inaddr_buf[0] = '\0';
            }
          return inaddr_buf;
      }
    return NULL;
}

/* Log the script name of failing that, the CWD. */
static void
log_script_name (const char *function)
{
    int pid = (int) getpid ();
    int uid = (int) geteuid ();
    char *script = getenv ("SCRIPT_FILENAME");
    if (script)
      {
          syslog (LOG_INFO, "%s: pid %d uid %d %s: script = '%s'", LOG_PREFIX,
                  pid, uid, function, strmaxlen (script, MAX_LOG_LEN));
      }
    else
      {
          char cwd[LINE_MAX];
          getcwd (cwd, LINE_MAX);
          syslog (LOG_INFO, "%s: pid %d uid %d %s: cwd = '%s'", LOG_PREFIX, 
                  pid, uid, function, strmaxlen (getcwd (cwd, LINE_MAX),
                                                   MAX_LOG_LEN));
      }
}

/* Log a given key value pair */
static void
log_key_value (const char *function, const char *key, const char *value)
{
    int pid = (int) getpid ();
    int uid = (int) geteuid ();
    syslog (LOG_INFO, "%s: pid %d uid %d %s: %s = '%s'", LOG_PREFIX, 
            pid, uid, function, key, value);
}

/* Log a given key array pair */
static void
log_key_array (const char *function, const char *key, char *const array[])
{
    int i = 0, size = strlen (key) + 5;
    char buff[size];
    while (array[i] != NULL && *array[i] && *array[i] != LASTMARK)
      {                         // This works...is it correct?
          snprintf (buff, size, "%s[%d]", key, i);
          log_key_value (function, buff, array[i]);
          i++;
      }
}

/* Given buffer, find all printable characters and place them in txtbuff.
 * Similar to the strings command which STRING_ISGRAPHIC was borrowed from.
 **/
static void
log_void_buffer (const char *function, char *out, const void *buffer,
                 size_t length)
{
    const char *in = (char *) buffer;
    int c = 0, i = 0;
    long cur;
    for (; c < length; c++)
      {
          cur = *in++;
          if (STRING_ISGRAPHIC (cur))
            {
                out[i++] = cur;
            }
          else if (i > 0 && cur == '\n')
            {
                out[i] = '\0';
                log_key_value (function, "void buffer", out);
                i = 0;
            }
      }
    if (i > 0)
      {
          out[i] = '\0';
          log_key_value (function, "void buffer", out);
      }
}

/* If we are going to log a connection, logging_outgoing will be set to
 * one in connect(). send and close use this to determine if they should
 * be logging. send uses this to count how many times it has logged so
 * it does not log more than the configured number of sends per connect().
 *
 * This code would get confused if you had two connections open at once.
 **/
int
connect (int socket, const struct sockaddr *address, socklen_t address_len)
{
    static void *(*func) ();
    if (!func)
      {
          func = (void *(*)()) dlsym (RTLD_NEXT, __FUNCTION__);
      }
    if (configurable_log_outgoing && logging_current_user ())
      {
          if (address->sa_family == AF_INET || address->sa_family == AF_INET6)
            {
                char *ip = get_ip (address);
                if (logging_this_ip (ip))
                  {
                      start_logging_fd (socket);
                      log_script_name (__FUNCTION__);
                      log_key_value (__FUNCTION__, "ip address", ip);
                  }
            }
      }
    return (int) func (socket, address, address_len);
}

ssize_t
send (int socket, const void *buffer, size_t length, int flags)
{
    static void *(*func) ();
    if (!func)
      {
          func = (void *(*)()) dlsym (RTLD_NEXT, __FUNCTION__);
      }
    int logging_outgoing = logging_this_fd (socket);
    /* logging_outgoing is 1 based and configurable_num_sends_logged
     * is 0 based so we need to use <=  to account for this */
    if (logging_outgoing && logging_outgoing <= configurable_num_sends_logged)
      {
          char cbuffer[length + 1];
          log_void_buffer (__FUNCTION__, cbuffer, buffer, length);
      }
    return (ssize_t) func (socket, buffer, length, flags);
}

ssize_t
write (int fd, const void *buffer, size_t length)
{
    static void *(*func) ();
    if (!func)
      {
          func = (void *(*)()) dlsym (RTLD_NEXT, __FUNCTION__);
      }
    int logging_outgoing = logging_this_fd (fd);
    /* logging_outgoing is 1 based and configurable_num_sends_logged
     * is 0 based so we need to use <=  to account for this */
    if (logging_outgoing && logging_outgoing <= configurable_num_sends_logged)
      {
          char cbuffer[length + 1];
          log_void_buffer (__FUNCTION__, cbuffer, buffer, length);
      }
    return (ssize_t) func (fd, buffer, length);
}

int
close (int fd)
{
    int pid = (int) getpid ();
    int uid = (int) geteuid ();
    static void *(*func) ();
    if (!func)
      {
          func = (void *(*)()) dlsym (RTLD_NEXT, __FUNCTION__);
      }
    int logging_outgoing = logging_this_fd (fd);
    if (logging_outgoing)
      {
          syslog (LOG_INFO, "%s: pid %d uid %d %s: %d", LOG_PREFIX, pid, uid,
                  __FUNCTION__, fd);
          stop_logging_fd (fd);
      }
    return (int) func (fd);
}

FILE *
popen (const char *command, const char *mode)
{
    static void *(*func) ();
    if (!func)
      {
          func = (void *(*)()) dlsym (RTLD_NEXT, __FUNCTION__);
      }
    if (configurable_log_popen && logging_current_user ())
      {
          log_script_name (__FUNCTION__);
          log_key_value (__FUNCTION__, "command", command);
      }
    return (FILE *) func (command, mode);
}

/* Execute PATH with all arguments after PATH until
 *    a NULL pointer and environment from `environ'.  */
int
execl (const char *path, const char *arg, ...)  // stolen from glibc
{
    size_t argv_max = INITIAL_ARGV_MAX;
    const char *initial_argv[INITIAL_ARGV_MAX];
    const char **argv = initial_argv;
    va_list args;

    argv[0] = arg;

    va_start (args, arg);
    unsigned int i = 0;
    while (argv[i++] != NULL)
      {
          if (i == argv_max)
            {
                argv_max *= 2;
                const char **nptr =
                    realloc (argv == initial_argv ? NULL : argv,
                             argv_max * sizeof (const char *));
                if (nptr == NULL)
                  {
                      if (argv != initial_argv)
                          free (argv);
                      return -1;
                  }
                if (argv == initial_argv)
                    /* We have to copy the already filled-in data ourselves.  */
                    memcpy (nptr, argv, i * sizeof (const char *));

                argv = nptr;
            }

          argv[i] = va_arg (args, const char *);
      }
    va_end (args);

    /* my feible code... */
    static int (*func) ();
    if (!func)
      {
          func = dlsym (RTLD_NEXT, __FUNCTION__);
      }
    if (configurable_log_execl && logging_current_user ())
      {
          log_script_name (__FUNCTION__);
          log_key_value (__FUNCTION__, "filename", path);
          log_key_array (__FUNCTION__, "argv", (char *const *) argv);
      }
    int ret = func (path, (char *const *) argv);
    /* ok, I am done */

/* Orginal
 * int ret = __execve (path, (char *const *) argv, __environ);
 */
    if (argv != initial_argv)
        free (argv);
    return ret;
}


int
execv (const char *filename, char *const argv[])
{
    static int (*func) ();
    if (!func)
      {
          func = dlsym (RTLD_NEXT, __FUNCTION__);
      }
    if (configurable_log_execv && logging_current_user ())
      {
          log_script_name (__FUNCTION__);
          log_key_value (__FUNCTION__, "filename", filename);
          log_key_array (__FUNCTION__, "argv", argv);
      }
    return (int) func (filename, argv);
}

/* Execute PATH with all arguments after PATH until a NULL pointer,
 *    and the argument after that for environment.  */
int
execle (const char *path, const char *arg, ...) // stolen from glibc
{
    size_t argv_max = INITIAL_ARGV_MAX;
    const char *initial_argv[INITIAL_ARGV_MAX];
    const char **argv = initial_argv;
    va_list args;
    argv[0] = arg;

    va_start (args, arg);
    unsigned int i = 0;
    while (argv[i++] != NULL)
      {
          if (i == argv_max)
            {
                argv_max *= 2;
                const char **nptr =
                    realloc (argv == initial_argv ? NULL : argv,
                             argv_max * sizeof (const char *));
                if (nptr == NULL)
                  {
                      if (argv != initial_argv)
                          free (argv);
                      return -1;
                  }
                if (argv == initial_argv)
                    /* We have to copy the already filled-in data ourselves.  */
                    memcpy (nptr, argv, i * sizeof (const char *));

                argv = nptr;
            }

          argv[i] = va_arg (args, const char *);
      }

    const char *const *envp = va_arg (args, const char *const *);
    va_end (args);

    /* my feible code... */
    static int (*func) ();
    if (!func)
      {
          func = dlsym (RTLD_NEXT, __FUNCTION__);
      }
    if (configurable_log_execlp && logging_current_user ())
      {
          log_script_name (__FUNCTION__);
          log_key_value (__FUNCTION__, "filename", path);
          log_key_array (__FUNCTION__, "argv", (char *const *) argv);
      }
    int ret = func (path, (char *const *) argv, (char *const *) envp);
    /* ok, I am done */

/* Orginal
 * int ret = __execve (path, (char *const *) argv, (char *const *) envp);
 **/
    if (argv != initial_argv)
        free (argv);

    return ret;
}


int
execve (const char *filename, char *const argv[], char *const envp[])
{
    static int (*func) ();
    if (!func)
      {
          func = dlsym (RTLD_NEXT, __FUNCTION__);
      }
    if (configurable_log_execve && logging_current_user ())
      {
          log_script_name (__FUNCTION__);
          log_key_value (__FUNCTION__, "filename", filename);
          log_key_array (__FUNCTION__, "argv", argv);
          if (configurable_verbose)
            {
                log_key_array (__FUNCTION__, "envp", envp);
            }
      }
    return (int) func (filename, argv, envp);
}

/* Execute FILE, searching in the `PATH' environment variable if
 *    it contains no slashes, with all arguments after FILE until a
 *       NULL pointer and environment from `environ'.  */
int
execlp (const char *file, const char *arg, ...) // stolen from glibc
{
    size_t argv_max = INITIAL_ARGV_MAX;
    const char *initial_argv[INITIAL_ARGV_MAX];
    const char **argv = initial_argv;
    va_list args;

    argv[0] = arg;

    va_start (args, arg);
    unsigned int i = 0;
    while (argv[i++] != NULL)
      {
          if (i == argv_max)
            {
                argv_max *= 2;
                const char **nptr =
                    realloc (argv == initial_argv ? NULL : argv,
                             argv_max * sizeof (const char *));
                if (nptr == NULL)
                  {
                      if (argv != initial_argv)
                          free (argv);
                      return -1;
                  }
                if (argv == initial_argv)
                    /* We have to copy the already filled-in data ourselves.  */
                    memcpy (nptr, argv, i * sizeof (const char *));

                argv = nptr;
            }

          argv[i] = va_arg (args, const char *);
      }
    va_end (args);

    /* my feible code... */
    static int (*func) ();
    if (!func)
      {
          func = dlsym (RTLD_NEXT, __FUNCTION__);
      }
    if (configurable_log_execlp && logging_current_user ())
      {
          log_script_name (__FUNCTION__);
          log_key_value (__FUNCTION__, "filename", file);
          log_key_array (__FUNCTION__, "argv", (char *const *) argv);
      }
    int ret = (int) func (file, (char *const *) argv);
    /* ok, I am done */

/* Original
 * int ret = execvp (file, (char *const *) argv);
 **/
    if (argv != initial_argv)
        free (argv);

    return ret;
}

int
execvp (const char *filename, char *const argv[])
{
    static int (*func) ();
    if (!func)
      {
          func = dlsym (RTLD_NEXT, __FUNCTION__);
      }
    if (configurable_log_execvp && logging_current_user ())
      {
          log_script_name (__FUNCTION__);
          log_key_value (__FUNCTION__, "filename", filename);
          log_key_array (__FUNCTION__, "argv", argv);
      }
    return (int) func (filename, argv);
}
